//StaticInstancedModel.h
//Created 14/12/13
//Created By Daniel Bowler
//
//Essentially extends StaticModel by supporting hardware instancing. This is
//the model of choice if you expect to render the same model multiple times.
//A good example of this would be a model of a tree - say you wanted to
//draw it 50 times in a forrest, you would want to use this class over
//StaticModel as this reduces the draw call count to 1 rather than up to 50 (Draw
//calls are bloody expensive!)
//
//Makes use of the mapping API to enable frustum culling. 
//
//NOTE: If you are using one of these InstancedModels and find that your performance
//sucks, check to see if you are in Debug or Release mode. The code optimisation 
//for release mode SERIOUSLY improves performance! (Up to 100x improvements in FPS
//when using >500 instances of an object)

#pragma once

//Include vector
#include <vector>

//Include d3dutil - Includes XNA and SafeDelete etc
#include "d3dUtil.h"
#include "xnacollision.h"

//Forward declare graphics interface so we can make it a friend. 
class GraphicsInterface;
//Forward declare StaticModel
class StaticModel;

//Forward declare structures from StaticModel
struct StaticModelBasicMaterial;
struct TessellationData;

//The data for each instance. 
struct InstanceData
{
	InstanceData()
	{
		Pos = XMFLOAT3(0.0f, 0.0f, 0.0f);
		Rot = XMFLOAT3(0.0f, 0.0f, 0.0f);
		Scale = XMFLOAT3(1.0f, 1.0f, 1.0f);

		Active = true;
	}

	//World matrix
	XMFLOAT3 Pos;
	XMFLOAT3 Rot;
	XMFLOAT3 Scale;

	//Active? - IE, to be rendered if it passes the frustum culling test anyway.
	bool Active;
};

class StaticInstancedModel
{
	//So the graphics interface can access private data - semi-hack, but
	//it does reduce the number of getters this class has to have. 
	friend GraphicsInterface;
public:
	//Constructor - The integer you pass in indicates the maximum number
	//of instances you intend to have of this model - Currently this
	//is constant as a run time change would require an expensive
	//buffer rebuild. 
	StaticInstancedModel(ID3D11Device* device, unsigned maxInstanceCount);
	~StaticInstancedModel();

	//These functions load the geometry for the StaticInstancedModel
	//class - Essentially, just passes the instruction to StaticModel.
	//
	//The rest of the class simply extends this by adding support for hardware instancing. 
	//
	//See StaticModel.h for detailed comments. 
	//
	//Loads standard geometry:
	void CreateBox(float width, float height, float depth,
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateSphere(float radius, unsigned sliceCount, unsigned stackCount,
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateGeoSphere(float radius, unsigned subDivisions, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateCollum(float bottomRadius, float topRadius, float height,
		unsigned sliceCount, unsigned stackCount, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device);
	void CreateGrid(float width, float height, 
		unsigned numVerticiesM, unsigned numVerticiesN, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device);

	//Loads .obj file (through my OBJ Loader). 
	void CreateModelFromOBJFile(
		ID3D11Device* device, 
		LPCWSTR objFilePath,					     
		std::vector<LPCWSTR> &objMaterialFilePath,	 
		std::string textureFolderPath,		
		bool useDisplacementMapping,	             
		TessellationData* tessData = 0,	             
		XMFLOAT2 texScale = XMFLOAT2(1.0f, 1.0f));   

	//Loads a model through assimp. Assimp supports all sorts of file formats 
	//including obj, 3ds and blend.
	//
	//Note that this function will ignore animation data.
	//
	//The bool flags are used to determin if post processing should occur. Generally, the
	//default values will be correct... if the model doesnt look right, it maybe worth experimenting
	//with the 4 flags. 
	void CreateModelThroughAssimp(ID3D11Device* device, 
		std::string& file, std::string& textureDirectory, 
		bool shouldMakeLeftHanded = true, bool shouldFlipUV = true, bool shouldFlipWinding = true, 
		bool shouldFixInwardFacingNormals = false, 
		bool loadDisplacementMapIfAvailable = false);
	
public:
	//Function that creates an instance by setting the world
	//matrix. When active == true, it will be rendered. Otherwise, it
	//wont be. 
	//
	//Returns false if a new instance has not been created because you already
	//have too many. 
	bool CreateInstance(XMFLOAT3 position, XMFLOAT3 rotation = XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3 scale = XMFLOAT3(1.0f, 1.0f, 1.0f), bool active = true);
	//Alternative method
	bool CreateInstance(InstanceData data);

	//Functions that update an instances orientation/position - If you want
	//to move one of them around the scene
	//
	//Returns false if instanceID > numInstances - no update will occur.
	//True if it succeeds and an update did occur.  
	bool UpdateInstance(unsigned instanceID, XMFLOAT3 newPosition, 
		XMFLOAT3 newRotation = XMFLOAT3(0.0f, 0.0f, 0.0f),
		XMFLOAT3 newScale = XMFLOAT3(1.0f, 1.0f, 1.0f), bool newActiveState = true);
	//Alternative to bove
	bool UpdateInstance(unsigned instanceID, InstanceData data);

	//Function that returns data for a given instance - Enters infinite loop
	//if you supply an instanceID > instanceCount. 
	void GetInstanceData(unsigned instanceID, 
		XMFLOAT3 &position, XMFLOAT3 &rotation, XMFLOAT3 &scale, bool &active);
	//Function that returns a ppointer to the InstanceData requested. Returns 0
	//if it can not be found. You can then update the InstanceData directly - Be careful!!!
	//Check for NULL before you go ahead and update. 
	InstanceData* GetInstanceData(unsigned instanceID);
	
	//Alternative to the above functions - [] is overloaded. Note that
	//if you create the StaticInstancedModel dynamically (new), you will
	//have to write something like this: 
	//
	//iMod[0][1]->... to get access to the instanced data pointer.
	//
	//Alternativly: 
	//StaticInstancedModel m = *myInstModel;
	//m[2]->... Access second piece of instance data.
	InstanceData* operator[](unsigned index);

	//Modified: 06/01/14 - You can now change the (maximum) number of 
	//instances that this class will handle. We will destroy and reallocate
	//our instance buffer making this fairly slow. Avoid as much as possible - its
	//going to be much better to set the maxInstanceCount higher than required than continuously
	//modifying the size.
	//
	//For the time being, thisfunction has no effect if you attempt to 
	//reduce the maximum number of instances this instance will handle. (By passing
	//a value that is NOT > maxNumInstances. 
	void ChangeMaximumInstances(ID3D11Device* device, unsigned newInstanceLimit);

public:
	//Returns the number of instances created for this class
	unsigned ReturnInstanceCount(){return instances.size();};
	//Return the maximum number of instances created for this class
	unsigned ReturnMaxInstanceCount(){return maxNumInstances;};

public:
	//Sets tessellation factor manually. Used for models loaded through assimp. 
	//Every submodel will share the same tessellation factors. 
	void SetAssimpModelsTessellationData(TessellationData& tessData);
	//Modifies material data.
	//
	//Firstly, updates all of the submodels materials with the same material
	void SetSubmodelsWithSameMaterial(Material& material);
	//Secondly, sets indivdual submodel (index) material.
	void SetSubModelMaterial(unsigned index, Material& material);
	//Thirdly, set reflection property for all submodels.
	void SetSubmodelsWithSameReflectionData(XMFLOAT3 &reflectivity);

public:
	//Simply returns the AABB in local space. 
	XNA::AxisAlignedBox GetAABBL();

	//Calculates an AABB in world space
	XNA::AxisAlignedBox CalculateWorldSpaceAABB(unsigned instanceID);

	//Compute OABB for use with collision detection
	XNA::OrientedBox CalculateWorldSpaceOABB(unsigned instanceID);

private:
	//Pointer to our static model. 
	StaticModel* staticModel;

	//True once we have loaded our vertex data - prevents us loading a mesh
	//and then trying to load another mesh in to the same StaticInstancedModel
	//instance. 
	bool isInit;

	//Max number of instances - used when we create the instance vertex buffer.
	unsigned maxNumInstances;
	//System copy of world matricies (and active flag) - One per instance. .size()
	//here will return the number of instances that have been created (IE, number
	//of times the programmer has called CreateInstance())
	std::vector<InstanceData>instances;

	//Vertex buffer of per instance data - IE, our 4x4 world matrix per instance. 
	//We will be building this each frame in order to support frustum culling - this
	//is done via the Mapping API. It's somewhat slow, but rather cute nontheless.
	//(It will have some performance gain - especially if the model uses
	//tessellation/geometry shaders)
	//
	//GraphicsInterface will fill this buffer per frame.
	ID3D11Buffer* instancedBuffer;
	//Number of instances that will be drawn - Ie, how many pass
	//the frsutum test. This is set by the GraphicsInterface
	unsigned numInstancesPassedFrustumTest;
};